Flip string to monotone increasing¶
Time: O(N); Space: O(1); medium
A string of ’0’s and ’1’s is monotone increasing if it consists of some number of ’0’s (possibly 0), followed by some number of ’1’s (also possibly 0.)
We are given a string S of ‘0’s and ’1’s, and we may flip any ’0’ to a ‘1’ or a ‘1’ to a ‘0’.
Return the minimum number of flips to make S monotone increasing.
Example 1:
Input: S = “00110”
Output: 1
Explanation:
We flip the last digit to get 00111
Example 2:
Input: S = “010110”
Output: 2
Explanation:
We flip to get 011111, or alternatively 000111
Example 3:
Input: S = “00011000”
Output: 2
Explanation:
We flip to get 00000000
Constraints:
1 <= len(S) <= 20000
S only consists of ‘0’ and ‘1’ characters
1. Prefix Sums [O(N), O(N)]¶
Intuition
For say a 5 digit string, the answer is either ‘00000’, ‘00001’, ‘00011’, ‘00111’, ‘01111’, or ‘11111’. Let’s try to calculate the cost of switching to that answer. The answer has two halves, a left (zero) half, and a right (one) half.
Evidently, it comes down to a question of knowing, for each candidate half: how many ones are in the left half, and how many zeros are in the right half.
We can use prefix sums. Say P[i+1] = A[0] + A[1] + … + A[i], where A[i] = 1 if S[i] == ‘1’, else A[i] = 0. We can calculate P in linear time.
Then if we want x zeros followed by N-x ones, there are P[x] ones in the start that must be flipped, plus (N-x) - (P[N] - P[x]) zeros that must be flipped. The last calculation comes from the fact that there are P[N] - P[x] ones in the later segment of length N-x, but we want the number of zeros.
Algorithm
For example, with S = “010110”: we have P = [0, 0, 1, 1, 2, 3, 3]. Now say we want to evaluate having x=3 zeros.
There are P[3] = 1 ones in the first 3 characters, and P[6] - P[3] = 2 ones in the later N-x = 3 characters.
So, there is (N-x) - (P[N] - P[x]) = 1 zero in the later N-x characters.
We take the minimum among all candidate answers to arrive at the final answer.
[3]:
class Solution1(object):
"""
Time: O(N), where N is the length of S
Space: O(N)
"""
def minFlipsMonoIncr(self, S):
"""
:type S: str
:rtype: int
"""
P = [0]
for x in S:
P.append(P[-1] + int(x))
return min(P[j] + len(S)-j-(P[-1]-P[j])
for j in range(len(P)))
[4]:
s = Solution1()
S = "00110"
assert s.minFlipsMonoIncr(S) == 1
S = "010110"
assert s.minFlipsMonoIncr(S) == 2
S = "00011000"
assert s.minFlipsMonoIncr(S) == 2
2. Dynamic Programming [O(N), O(1)]¶
[5]:
class Solution2(object):
"""
Time: O(N)
Space: O(1)
"""
def minFlipsMonoIncr(self, S):
"""
:type S: str
:rtype: int
"""
flip0, flip1 = 0, 0
for c in S:
flip0 += int(c == '1')
flip1 = min(flip0, flip1 + int(c == '0'))
return flip1
[6]:
s = Solution2()
S = "00110"
assert s.minFlipsMonoIncr(S) == 1
S = "010110"
assert s.minFlipsMonoIncr(S) == 2
S = "00011000"
assert s.minFlipsMonoIncr(S) == 2